package com.douma.util.rsa;

import com.douma.util.base64.Base64Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 加密加签解密验签工具类
 */
@Slf4j
public class RSAUtil {

    public static final String  KEY_ALGORITHM = "RSA";
    private static final String PUBLIC_KEY    = "RSAPublicKey";
    private static final String PRIVATE_KEY   = "RSAPrivateKey";
    private static final String ALG_RSA       = "RSA";
    private static final String ALG_DSA       = "DSA";

    /**
     * 初始化钥参数
     * 
     * @return
     * @throws Exception
     */
    public static Map<String, Object> initKey() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**
     * 获得公钥字符串
     * 
     * @param keyMap 钥初始化参数
     * @return
     * @throws Exception
     */
    public static String getPublicKeyStr(Map<String, Object> keyMap) throws Exception {
        //获得map中的公钥对象 转为key对象
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        //编码返回字符串
        return encryptBASE64(key.getEncoded());
    }

    /**
     * 获得私钥字符串
     * 
     * @param keyMap 钥初始化参数
     * @return
     * @throws Exception
     */
    public static String getPrivateKeyStr(Map<String, Object> keyMap) throws Exception {
        //获得map中的私钥对象 转为key对象
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        //编码返回字符串
        return encryptBASE64(key.getEncoded());
    }

    /**
     * RSA 加密
     *
     * @param data 需要加密字符串
     * @param publicKey 加密秘钥
     * @return 加密后字符串
     */
    public static String encrypt(String data, String publicKey) {
        String encryptData;
        try {
            KeyFactory keyFac = KeyFactory.getInstance("RSA");
            PublicKey pubKey = keyFac.generatePublic(new X509EncodedKeySpec(decode(publicKey)));
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            byte[] result = cipher(data.getBytes("UTF-8"), cipher, getBlockSize(pubKey) - 11);
            encryptData = encode(result);
        } catch (Exception e) {
            log.warn("EncryptUtil.rsa error:{}", e);
            encryptData = null;
        }
        return encryptData;
    }

    /**
     * RSA 解密
     * 
     * @param encryptedData
     * @param privateKey
     * @return
     */
    public static String decrypt(String encryptedData, String privateKey) {
        String decryptData;
        try {
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Util.decode(privateKey));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey priKey = keyFactory.generatePrivate(keySpec);
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            byte[] result = cipher(Base64Util.decode(encryptedData), cipher, getBlockSize(priKey));
            decryptData = new String(result, "UTF-8");
        } catch (Exception e) {
            log.warn("EncryptUtil.decrypt error:{}", e);
            decryptData = null;
        }
        return decryptData;
    }

    /**
     * 加签
     *
     * @param params 参与签名的字段集合
     * @param appId appId
     * @return 签名串
     */
    @SuppressWarnings("rawtypes")
    public static String sign(HashMap params, String appId) {
        Object[] keySet = params.keySet().toArray();
        Arrays.sort(keySet);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < keySet.length; i++) {
            if (!"sign".equals(keySet[i])) {
                sb.append(keySet[i]).append("=");
                sb.append(params.get(keySet[i])).append("&");
            }
        }
        return getMD5(sb.deleteCharAt(sb.lastIndexOf("&")).toString() + appId);
    }

    /**
     * 验签
     * 
     * @param encrypable 验签对象实体
     * @param appId 约定appid
     * @return
     */
    public static Boolean checkSign(Encrypable encrypable, String appId) {
        if (encrypable == null && StringUtils.isBlank(appId)) {
            log.error("EncryptUtil.checkSign:encrypable and appId can not be null");
            throw new RuntimeException("encrypable and appId can not be null");
        }
        Class clazz = encrypable.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        try {
            if (declaredFields != null && declaredFields.length > 0) {

                HashMap params = new HashMap();
                for (Field field : declaredFields) {
                    EncrypField encrypField = field.getAnnotation(EncrypField.class);
                    if (encrypField != null) {
                        field.setAccessible(true);
                        params.put(field.getName(),
                                field.get(encrypable) != null ? field.get(encrypable).toString() : null);
                    }
                }
                if (params.size() == 0) {
                    throw new RuntimeException("params is empty");
                }
                return RSAUtil.sign(params, appId).equals(encrypable.getAppointSign());
            } else {
                return false;
            }
        } catch (Exception e) {
            log.error("EncryptUtil.checkSign:encryp error,e={}", e);
            throw new RuntimeException("encryp error");
        }
    }

    /**
     * 对字符串md5加密(小写+字母)
     *
     * @param str 传入要加密的字符串
     * @return MD5加密后的字符串
     */
    public static String getMD5(String str) {
        if (str == null) {
            return null;
        }
        try {
            StringBuilder md5StrBuff = new StringBuilder("");
            // 生成一个MD5加密计算摘要
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.reset();
            md.update(str.getBytes("UTF-8"));
            byte[] byteArray = md.digest();
            for (int i = 0; i < byteArray.length; i++) {
                if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)
                    md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));
                else
                    md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
            }
            return md5StrBuff.toString();
        } catch (Exception e) {
            log.warn("EncryptUtil.getMD5 error:{}", e);
            return null;
        }
    }

    /**
     * 将BASE64字符串恢复为二进制数据
     *
     * @param base64String
     * @return
     */
    public static byte[] decode(String base64String) {
        try {
            return Base64.decodeBase64(base64String.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            log.warn("EncryptUtil.decode error={}", e);
            return null;
        }
    }

    /**
     * 将二进制数据编码为BASE64字符串
     *
     * @param binaryData
     * @return
     */
    public static String encode(byte[] binaryData) {
        try {
            return new String(Base64.encodeBase64(binaryData), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.warn("EncryptUtil.encode error={}", e);
            return null;
        }
    }

    private static String encryptBASE64(byte[] key) throws Exception {
        return (new BASE64Encoder()).encodeBuffer(key);
    }

    private static byte[] cipher(byte[] data, Cipher cipher, int blockSize) throws Exception {
        final ByteArrayInputStream in = new ByteArrayInputStream(data);
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final byte[] cache = new byte[blockSize];
        while (true) {
            final int r = in.read(cache);
            if (r < 0) {
                break;
            }
            final byte[] temp = cipher.doFinal(cache, 0, r);
            out.write(temp, 0, temp.length);
        }
        return out.toByteArray();
    }

    private static int getBlockSize(final Key key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        final String alg = key.getAlgorithm();
        final KeyFactory keyFactory = KeyFactory.getInstance(alg);
        if (key instanceof PublicKey) {
            final BigInteger prime;
            if (ALG_RSA.equals(alg)) {
                prime = keyFactory.getKeySpec(key, RSAPublicKeySpec.class).getModulus();
            } else if (ALG_DSA.equals(alg)) {
                prime = keyFactory.getKeySpec(key, DSAPublicKeySpec.class).getP();
            } else {
                throw new NoSuchAlgorithmException("不支持的解密算法：" + alg);
            }
            return prime.toString(2).length() / 8;
        } else if (key instanceof PrivateKey) {
            final BigInteger prime;
            if (ALG_RSA.equals(alg)) {
                prime = keyFactory.getKeySpec(key, RSAPrivateKeySpec.class).getModulus();
            } else if (ALG_DSA.equals(alg)) {
                prime = keyFactory.getKeySpec(key, DSAPrivateKeySpec.class).getP();
            } else {
                throw new NoSuchAlgorithmException("不支持的解密算法：" + alg);
            }
            return prime.toString(2).length() / 8;
        } else {
            throw new RuntimeException("不支持的密钥类型：" + key.getClass());
        }
    }
}
